﻿using System;
using Microsoft.Xna.Framework;

namespace ShevaEngine.Core.Modules.Graphics.Cameras
{
    /// <summary>
    /// Kamera typu Orbit.
    /// </summary>
    public class OrbitCameraController
    {
        /// <summary>Camera.</summary>
        public Camera Camera;

        /// <summary>Pozice kamery.</summary>
        private Microsoft.Xna.Framework.Vector3 CameraPosition;
        /// <summary>Pohled kamery.</summary>
        private Vector3 _TarPosition;
        /// <summary>Speed factor pri manipulaci s kurzorem mysi.</summary>
        private float _SpeedFactor = 0.05f;
        /// <summary>Rotacni matice.</summary>
        private Matrix _Rotation = Matrix.Identity;
        /// <summary>Aktualni hodnota zoomu.</summary>
        private float _Zoom = 15f;
        /// <summary>Aktualni hodnota horizontalniho uhlu.</summary>
        public float HorizontalAngle = MathHelper.Pi;
        /// <summary>Aktualni hodnota vertikalniho uhlu.</summary>
        public float VerticalAngle = MathHelper.PiOver4;

        /// <summary>Minimalni hodnota vertikalniho uhlu.</summary>
        private const float VerticalAngleMin = 0.01f;
        /// <summary>Maximalni hodnota vertikalniho uhlu.</summary>
        private const float VerticalAngleMax = (MathHelper.Pi / 2) - 0.01f;
        /// <summary>Minimalni hodnota zoomu.</summary>
        private const float ZoomMin = 0.1f;
        /// <summary>Maximalni hodnota zoomu.</summary>
        private const float ZoomMax = 100.0f;        


        /// <summary>
        /// Cilovy bod pohledu kamery.
        /// </summary>
        public Microsoft.Xna.Framework.Vector3 TargetPosition
        {
            get { return this.CameraPosition + this._Rotation.Forward; }
            set
            {
                this._TarPosition = value;
                this.Update();
            }
        }

        /// <summary>
        /// Cilovy bod pohledu kamery.
        /// </summary>
        public Microsoft.Xna.Framework.Vector3 UpVector
        {
            get { return this._Rotation.Up; }
        }
       

        /// <summary>
        /// Konstruktor.
        /// </summary>
        public OrbitCameraController(Camera camera)
        {
            this.Camera = camera;

            this._TarPosition = new Vector3(0, 0, 0);
            
            this.Update();            
        }

        /// <summary>
        /// Provede posun kamery.
        /// </summary>
        /// <param name="difference"></param>
        public void Move(Vector2 difference)
        {
            this._TarPosition -= new Vector3(
                ((float)System.Math.Cos(this.HorizontalAngle) * difference.X +
                 (float)System.Math.Sin(this.HorizontalAngle) * difference.Y) * this._SpeedFactor,
                 0,
                ((float)System.Math.Sin(this.HorizontalAngle) * -difference.X +
                 (float)System.Math.Cos(this.HorizontalAngle) * +difference.Y) * this._SpeedFactor);                

            this.Update();
        }

        /// <summary>
        /// Provede posun kamery.
        /// </summary>
        /// <param name="difference"></param>
        public void Rotate(Vector2 difference)
        {
            this.VerticalAngle -= difference.Y * this._SpeedFactor;
            this.HorizontalAngle -= difference.X * this._SpeedFactor;

            this.Update();
        }

        /// <summary>
        /// Provede posun kamery.
        /// </summary>
        /// <param name="difference"></param>
        public void Zoom(int difference)
        {
            this._Zoom += (float)difference * this._SpeedFactor;
            
            this.Update();
        }

        /// <summary>
        /// Metoda Update kamery.
        /// </summary>
        public void Update()
        {
            // Keep vertical angle within tolerances
            this.VerticalAngle = MathHelper.Clamp(this.VerticalAngle, VerticalAngleMin, VerticalAngleMax);

            // Keep vertical angle within PI
            if (this.HorizontalAngle > MathHelper.TwoPi)
                this.HorizontalAngle -= MathHelper.TwoPi;
            else if (HorizontalAngle < 0.0f)
                this.HorizontalAngle += MathHelper.TwoPi;

            // Keep zoom within range
            this._Zoom = MathHelper.Clamp(_Zoom, ZoomMin, ZoomMax);

            // Start with an initial offset
            Vector3 cameraPosition = new Vector3(0.0f, 0.0f, _Zoom);
            // Rotate vertically
            cameraPosition = Vector3.Transform(cameraPosition, Matrix.CreateRotationX(-this.VerticalAngle));
            // Rotate horizontally
            cameraPosition = Vector3.Transform(cameraPosition, Matrix.CreateRotationY(this.HorizontalAngle));

            this.CameraPosition = cameraPosition + this._TarPosition;
            this.LookAt(this._TarPosition);

            this.Camera.UpVector = this.UpVector;
            this.Camera.ViewMatrix = Matrix.CreateLookAt(this.CameraPosition, this.TargetPosition, this.UpVector);
        }

        /// <summary>
        /// Points camera in direction of any position.
        /// </summary>
        /// <param name="targetPos">Target position for camera to face.</param>
        public void LookAt(Vector3 targetPos)
        {
            Vector3 newForward = targetPos - this.CameraPosition;
            newForward.Normalize();
            this._Rotation.Forward = newForward;

            Vector3 referenceVector = Vector3.UnitY;

            // On the slim chance that the camera is pointer perfectly parallel with the Y Axis, we cannot
            // use cross product with a parallel axis, so we change the reference vector to the forward axis (Z).
            if (this._Rotation.Forward.Y == referenceVector.Y || this._Rotation.Forward.Y == -referenceVector.Y)
            {
                referenceVector = Vector3.UnitY;
            }

            this._Rotation.Right = Vector3.Cross(this._Rotation.Forward, referenceVector);
            this._Rotation.Up = Vector3.Cross(this._Rotation.Right, this._Rotation.Forward);
        }
    }
}
